package com.riven.redisson.concurrent;

import com.riven.redisson.annotation.LockSubject;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author riven
 * @since 2025-05-26
 */
@Slf4j
public class AnnotatedParameterLockSubjectResolver extends BaseLockSubjectResolver {

    private final Map<String, List<LockSubjectAnnotatedParameter>> cachedParameters = new ConcurrentHashMap<>(32);

    @Override
    public Object doResolve(MethodSignature methodSignature, Object[] args) {
        final String methodUniqueId = methodSignature.toLongString();
        List<LockSubjectAnnotatedParameter> cachedAnnotatedParameters = cachedParameters.get(methodUniqueId);
        if (cachedAnnotatedParameters == null) {
            Parameter[] parameters = methodSignature.getMethod().getParameters();
            List<LockSubjectAnnotatedParameter> annotatedParameters = new ArrayList<>();
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                LockSubject lockSubject = parameter.getDeclaredAnnotation(LockSubject.class);
                if (lockSubject != null) {
                    annotatedParameters.add(LockSubjectAnnotatedParameter.builder().index(i).spel(lockSubject.value()).order(lockSubject.order()).build());
                }
            }
            annotatedParameters.sort(Comparator.comparing(LockSubjectAnnotatedParameter::getOrder));
            cachedParameters.putIfAbsent(methodUniqueId, annotatedParameters);
            cachedAnnotatedParameters = annotatedParameters;
        }
        if (cachedAnnotatedParameters.isEmpty()) {
            //no annotated parameters
            return null;
        }
        List<Object> resolvedSubjects = new ArrayList<>(cachedAnnotatedParameters.size());
        for (int i = 0; i < cachedAnnotatedParameters.size(); i++) {
            Object argValue = args[i];
            LockSubjectAnnotatedParameter annotatedParameter = cachedAnnotatedParameters.get(i);
            String spelValue = annotatedParameter.getSpel();
            if (!StringUtils.hasText(spelValue) || argValue == null) {
                resolvedSubjects.add(argValue);
                continue;
            }
            try {
                final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
                final Expression expression = spelExpressionParser.parseExpression(spelValue);
                final StandardEvaluationContext evaluationContext = new StandardEvaluationContext(argValue);
                resolvedSubjects.add(expression.getValue(evaluationContext));
            } catch (ParseException | EvaluationException e) {
                log.warn("Error occurred while extract value from SpEl [{}],argValue=[{}],skipped it", spelValue, argValue);
            }
        }
        if (resolvedSubjects.isEmpty() || resolvedSubjects.stream().allMatch(Objects::isNull)) {
            //resolved nothing, don't lock
            return DO_NOT_LOCK;
        }
        return resolvedSubjects.stream()
                .map(item -> item == null ? "null" : item)
                .map(Object::toString)
                .collect(Collectors.joining("-"));
    }

    @Data
    @Builder
    private static class LockSubjectAnnotatedParameter {

        private int index;
        private int order;
        private String spel;

    }
}
